package com.jy.api.service.impl;

import com.alibaba.fastjson.JSON;
import com.jy.api.controller.admin.params.InventoryFlowRequest;
import com.jy.api.dao.*;
import com.jy.api.domain.*;
import com.jy.api.exception.MyException;
import com.jy.api.service.StockTransactionService;
import com.jy.api.util.RedisSyncLocker;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * Created by lihu on 2018/2/5.
 */
@Service
@Slf4j
public class StockTransactionServiceImpl implements StockTransactionService {

    @Autowired
    StockTransactionDao stockTransactionDao;

    @Autowired
    ProductDao productDao;

    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private RedisSyncLocker redisSyncLocker;

    @Autowired
    InventoryDao inventoryDao;

    @Autowired
    private DiscountOrderItemDao discountOrderItemDao;
    @Autowired
    private DiscountPromotionProductDao discountPromotionProductDao;

    @Autowired
    private MaterialUnitDao materialUnitDao;
    @Autowired
    private ProductMaterialDao productMaterialDao;
    @Resource
    private CacheManager cacheManager;
    @Autowired
    StockTransactionService stockTransactionService;

    @Override
    public void refundStockIn(Order order) throws Exception {
        log.debug("Start to record stock in data for order:{}", order.getId());
        HashMap<Integer, Object> map = new HashMap();
        HashMap productMap = new HashMap();
        HashMap productStockMap = new HashMap();
        HashMap<String, Object> stockMap = new HashMap();

        List<InventoryFlowRequest> requests = new ArrayList<>();
        for (OrderItem item : order.getItems()) {
            Product product = productDao.findOne(item.getProductId());
            if (product.isStockEnabled()) {
                if (stockMap.containsKey(item.getProductId())) {
                    stockMap.put(product.getId(), (Integer.parseInt(stockMap.get(product.getId()).toString()) + (item.getQuantity() * product.getProductQty())));
                } else {
                    stockMap.put(product.getId(), product.getProductQty() * item.getQuantity());
                }
                productStockMap.put(product.getId(), product);
            }
            for (ProductMaterialItem productMaterialItem : product.getProductMaterialItems()) {
                if (map.containsKey(productMaterialItem.getProductMaterialId())) {
                    map.put(productMaterialItem.getProductMaterialId(), (Integer.parseInt(map.get(productMaterialItem.getProductMaterialId()).toString()) + (item.getQuantity() * productMaterialItem.getQty())));
                } else {
                    map.put(productMaterialItem.getProductMaterialId(), (item.getQuantity() * productMaterialItem.getQty()));
                }
                productMap.put(productMaterialItem.getProductMaterialId(), product.getThreshold());
            }
        }
        for (String s : stockMap.keySet()) {
            Product product = (Product) productStockMap.get(s);
            InventoryFlowRequest stockOut = new InventoryFlowRequest();
            stockOut.setSkuId(product.getId() + "");
            stockOut.setSkuName(product.getName());
            stockOut.setSkuType(1);
            stockOut.setStockLevel(StockLevel.BAR);
            stockOut.setStoreId(product.getStoreId());
            stockOut.setType(StockType.STOCK_IN);
            stockOut.setSource(StockChangeReason.REFUND_IN);
            stockOut.setOperatorId(order.getCreatorId());
            stockOut.setOperatorName(order.getCreatorName());
            stockOut.setNotes(order.getRefundReason());
            stockOut.setMold(ProductMold.PRODUCT);
            stockOut.setRefId(order.getId());
            stockOut.setQty(Integer.parseInt(stockMap.get(s).toString()));
            requests.add(stockOut);
        }
        for (Integer integer : map.keySet()) {
            ProductMaterial productMaterial = productMaterialDao.findOne(integer);
            InventoryFlowRequest stockMaterialOut = new InventoryFlowRequest();
            stockMaterialOut.setSkuId(productMaterial.getId() + "");
            stockMaterialOut.setSkuName(productMaterial.getName());
            stockMaterialOut.setSkuType(2);
            stockMaterialOut.setStockLevel(StockLevel.BAR);
            stockMaterialOut.setStoreId(order.getStoreId());
            stockMaterialOut.setType(StockType.STOCK_IN);
            stockMaterialOut.setSource(StockChangeReason.REFUND_IN);
            stockMaterialOut.setOperatorId(order.getCreatorId());
            stockMaterialOut.setOperatorName(order.getCreatorName());
            stockMaterialOut.setNotes(order.getRefundReason());
            stockMaterialOut.setMold(ProductMold.MATERIAL);
            stockMaterialOut.setRefId(order.getId());
            stockMaterialOut.setQty(Integer.parseInt(map.get(integer).toString()));
            requests.add(stockMaterialOut);
        }


        List<DiscountOrderItem> discountOrderItems = discountOrderItemDao.findByOrderId(order.getId());
        List<DiscountPromotionProduct> discountPromotionProducts = new ArrayList<>();
        for (DiscountOrderItem item : discountOrderItems) {
            DiscountPromotionProduct promotionProduct = discountPromotionProductDao.findByProductIdAndPromotionId(item.getProductId(), item.getPromotionId());
            promotionProduct.setCount(promotionProduct.getCount() - item.getQty());
            item.setQty(-item.getQty());
            discountPromotionProducts.add(promotionProduct);
        }
        discountPromotionProductDao.save(discountPromotionProducts);
        discountOrderItemDao.save(discountOrderItems);
        flows(requests);
    }


    private String getInventoryLockedKey(String skuId) {
        return String.format("STC-%s", skuId);
    }


    @Override
    @Transactional
    public void flows(@Valid @NotNull @NotEmpty List<InventoryFlowRequest> requests) throws Exception {
        if (requests.size() > 0) {
            String storeId = requests.get(0).getStoreId();
            redisSyncLocker.keyLock("warehouseLock" + storeId, obj -> {
                log.info("库存批量变动参数{}", JSON.toJSONString(requests));
                List<StockChangeTransaction> transactions = new ArrayList<>();
                HashMap<String, Integer> skuTypeMap = new HashMap<>();
                final Cache cache = cacheManager.getCache("StockChangeTransaction");
                for (InventoryFlowRequest param : requests) {
                    final String surplusKey = String.format("surplus%s%s%s", param.getStoreId(), param.getSkuId(), param.getStockLevel());
                    Integer surplus = cache.get(surplusKey, Integer.class);
                    if (null == surplus) {
                        final StockChangeTransaction lastTransaction =
                                stockTransactionDao.findTop1ByStoreIdAndSkuIdAndStockLevelOrderByIdDesc(param.getStoreId(), param.getSkuId(), param.getStockLevel());
                        if (null == lastTransaction) {
                            log.info("商品id{}库存变动,无库存流水", param.getSkuId());
                            switch (param.getSkuType()) {
                                case 0:
                                    surplus = inventoryDao.findOne(Long.parseLong(param.getSkuId())).getQuantity();
                                    break;
                                case 1:
                                    surplus = productDao.findOne(param.getSkuId()).getQuantity();
                                    break;
                                case 2:
                                    surplus = productMaterialDao.findOne(Integer.parseInt(param.getSkuId())).getStockQty();
                                    break;
                            }
                        } else {
                            surplus = lastTransaction.getAllowance();
                        }
                    }
                    log.info("商品id{}库存变动,原数量{}变动原因{}", param.getSkuId(), surplus, param.getSource());
                    int offset = param.getQty();
                    StockChangeReason reason = param.getSource();
                    StockType type = param.getType();
                    if (StockChangeReason.CHECK_IN.equals(param.getSource()) || StockChangeReason.CHECK_OUT.equals(param.getSource())) {
                        offset = param.getQty() - surplus;
                        reason = offset >= 0 ? StockChangeReason.CHECK_IN : StockChangeReason.CHECK_OUT;
                        type = offset >= 0 ? StockType.STOCK_IN : StockType.STOCK_OUT;
                    }
                    log.info("商品id{}库存变动,变动数量{}变动原因{}", param.getSkuId(), offset, param.getSource());
                    final int newSurplus = surplus + offset;
                    if (newSurplus < 0) {
                        log.info("订单id{}库存变动异常，{}库存不足", param.getRefId(), param.getSkuName());
                        if (param.getStockLevel().equals(StockLevel.WAREHOUSE)){
                            throw new MyException(param.getSkuName()+"库存不足");
                        }

                    }

                    StockChangeTransaction transaction = new StockChangeTransaction();
                    transaction.setSkuId(param.getSkuId());
                    transaction.setSkuName(param.getSkuName());
                    transaction.setStockLevel(param.getStockLevel());
                    transaction.setStoreId(param.getStoreId());
                    transaction.setType(type);
                    transaction.setSource(reason);
                    transaction.setRefId(param.getRefId());
                    transaction.setOperatorId(param.getOperatorId());
                    transaction.setOperatorName(param.getOperatorName());
                    transaction.setNotes(param.getNotes());
                    transaction.setMold(param.getMold());
                    transaction.setUnitName(param.getUnitName());
                    transaction.setAllowance(newSurplus);
                    transaction.setQuantity(Math.abs(offset));
                    transaction.setOrgQty(surplus);
                    transactions.add(transaction);
                    skuTypeMap.put(param.getSkuId(), param.getSkuType());
                    log.info("商品id{}库存变动,原数量{},变动数量{}，变动原因{}", transaction.getSkuId(), surplus, offset, param.getSource());
                }
                stockTransactionDao.save(transactions);
                for (StockChangeTransaction transaction : transactions) {
                    final String surplusKey = String.format("surplus%s%s%s", transaction.getStoreId(), transaction.getSkuId(), transaction.getStockLevel());
                    Integer skuType = skuTypeMap.get(transaction.getSkuId());
                    if (null != cache) {
                        cache.put(surplusKey, transaction.getAllowance());
                    }
                    switch (skuType) {
                        case 0:
                            inventoryDao.updateQtyById(transaction.getAllowance(), Integer.parseInt(transaction.getSkuId()), transaction.getStoreId());
                            break;
                        case 1:
                            productDao.updateQtyById(transaction.getAllowance(), transaction.getSkuId(), transaction.getStoreId());
                            break;
                        case 2:
                            productMaterialDao.updateQtyById(transaction.getAllowance(), Integer.parseInt(transaction.getSkuId()), transaction.getStoreId());
                            break;
                    }
                }
                return null;
            }, null);
        }


    }

    @SneakyThrows
    @Override
    public int surplus(String storeId, String skuId, int type, StockLevel stockLevel) {
        return (int) redisSyncLocker.keyLock("warehouseLock" + storeId, o -> {
            final String surplusKey = String.format("surplus%s%s%s", storeId, skuId, stockLevel);
            final Cache cache = cacheManager.getCache("StockChangeTransaction");
            if (null != cache) {
                Integer surplus = cache.get(surplusKey, Integer.class);
                if (null == surplus) {
                    final StockChangeTransaction lastTransaction =
                            stockTransactionDao.findTop1ByStoreIdAndSkuIdAndStockLevelOrderByIdDesc(storeId, skuId, stockLevel);
                    if (null == lastTransaction) {
                        switch (type) {
                            case 0:
                                surplus = inventoryDao.findOne(Long.parseLong(skuId)).getQuantity();
                                break;
                            case 1:
                                surplus = productDao.findOne(skuId).getQuantity();
                                break;
                            case 2:
                                surplus = productMaterialDao.findOne(Integer.parseInt(skuId)).getStockQty();
                                break;
                        }
                    } else {
                        surplus = lastTransaction.getAllowance();
                    }
                    cache.put(surplusKey, surplus);
                    return cache.get(surplusKey, Integer.class);
                }
                return surplus;
            }
            return 0;
        }, null);
    }


}
